<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="Content-Style-Type" content="text/css">
<title></title>
<meta name="Generator" content="Cocoa HTML Writer">
<meta name="CocoaVersion" content="824.48">
<style type="text/css">
p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 18.0px Helvetica}
p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px}
p.p3 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica}
p.p4 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; color: #8a2617}
p.p5 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; color: #606060}
span.s1 {color: #4972b9}
span.s2 {color: #0047e8}
span.s3 {color: #4972ba}
span.s4 {color: #0000ff}
span.s5 {color: #0033ae}
span.s6 {color: #8a2617}
span.s7 {color: #000000}
span.s8 {color: #2862cf}
span.s9 {color: #1354dc}
span.s10 {color: #606060}
span.s11 {color: #4c6a27}
span.Apple-tab-span {white-space:pre}
</style>
</head>
<body>
<p class="p1"><b>SOMTrain<span class="Apple-tab-span">	</span><span class="Apple-tab-span">	</span>Create (train) a Self-Organising Map</b></p>
<p class="p2"><br></p>
<p class="p3"><span class="Apple-tab-span">	</span><b>SOMTrain.kr(bufnum, inputdata, netsize, numdims, traindur, nhood, gate, initweight)</b></p>
<p class="p2"><br></p>
<p class="p3">This UGen trains a <i>self-organising map</i> (a well-known type of neural net), which is a system which learns to map a high-dimensional feed of input data onto a lower-dimensional array of "nodes". The neural net is stored in a <a href="SC://Buffer"><span class="s1">Buffer</span></a>. Once trained, the net can be analysed or can be used to transform other incoming data (using <a href="SOMRd.html"><span class="s2">SOMRd</span></a>).</p>
<p class="p2"><br></p>
<p class="p3"><span class="Apple-converted-space"> </span>- <b>bufnum</b><span class="Apple-tab-span">	</span>- A reference to the buffer where the map will be created. Initialising the map is up to you (see below).</p>
<p class="p3"><span class="Apple-converted-space"> </span>- <b>inputdata</b><span class="Apple-tab-span">	</span>- An <a href="SC://Array"><span class="s3">Array</span></a> of the input signals for the net to learn.</p>
<p class="p3"><span class="Apple-converted-space"> </span>- <b>netsize</b><span class="Apple-tab-span">	</span>- The size of the neural net along one dimension.</p>
<p class="p3"><span class="Apple-converted-space"> </span>- <b>numdims</b> - The dimensionality of the neural net. Choose from 1, 2, 3, or 4.</p>
<p class="p3"><span class="Apple-converted-space"> </span>- <b>traindur</b><span class="Apple-tab-span">	</span>- The length of the training period; the number of data frames that will come in before the net gradually "freezes" into its final state</p>
<p class="p3"><span class="Apple-converted-space"> </span>- <b>nhood</b><span class="Apple-tab-span">	</span>- The <i>initial</i> size of the neighbourhood used in training. (The size always shrinks to zero as the training progresses.) The size is expressed as a fraction of <b>netsize</b>. Default is 0.5, and you probably don't need to change it.</p>
<p class="p3"><span class="Apple-converted-space"> </span>- <b>gate</b> <span class="Apple-tab-span">	</span>-<span class="Apple-converted-space">  </span>A simple on-or-off control. When off (gate&lt;=0) the incoming data is ignored.</p>
<p class="p3"><span class="Apple-converted-space"> </span>- <b>initweight</b><span class="Apple-tab-span">	</span>- How heavily the algorithm weights the data points at first (the weighting always tails off to zero as the training progresses, in a reciprocal curve). Default is 1, and you usually don't need to change this.</p>
<p class="p2"><br></p>
<p class="p3">The UGen outputs an array of three values: the number of data points still to come in before training finishes; the "reconstruction error" of the single data point that has most recently been input (the squared-distance between it and the node nearest it); and the frame index of the most recent matching node. The "reconstruction error" will vary a lot but should in general decrease as the net comes closer and closer to mapping the data well. The frame index gives a direct index into the Buffer, and can be converted to a multidimensional location in the SOM structure using <a href="SOMRd.html"><span class="s4">SOMRd</span></a>.bufIndexToCoords.</p>
<p class="p2"><br></p>
<p class="p3">Note: this UGen does <i>not</i> cope well if the buffer is freed or changed during running. For efficiency purposes, it doesn't keep checking the buffer while running; so you should avoid changing the buffer while the training is running.</p>
<p class="p2"><br></p>
<p class="p3"><b>BUFFER SIZE AND NUMBER OF NODES/DIMENSIONS:</b></p>
<p class="p2"><br></p>
<p class="p3">The number of nodes in the net is defined by netsize AND numdims.<span class="Apple-converted-space"> </span></p>
<p class="p3">If the netsize is 10 and the numdims is 2, the actual number of nodes is 10 x 10 = 100.<span class="Apple-converted-space"> </span></p>
<p class="p3">Or if numdims is 3, the number of nodes is 10 x 10 x 10 = 1000.</p>
<p class="p2"><br></p>
<p class="p3">The buffer must contain the same number of frames as the number of nodes; and it must have the same number of channels as the <b>inputdata</b> array. The SOMTrain class provides a convenience function for allocating a buffer of the right size:</p>
<p class="p2"><br></p>
<p class="p3"><span class="Apple-tab-span">	</span>~asuitablebuffer = <span class="s5">SOMTrain</span>.allocBuf(s, netsize, numdims, numinputdims); <span class="s6">// Calls Buffer.alloc on your behalf</span></p>
<p class="p2"><br></p>
<p class="p3"><b>INITIALISING THE MAP</b></p>
<p class="p2"><br></p>
<p class="p3">The values held by the SOM nodes must usually be initialised to some state before the training begins. In the literature on SOMs there are a couple of common options such as random intialisation, or initialisation using the principal components of the input data. It's up to you - since the map is just a regular Buffer object, you can use Buffer's loadCollection methods (etc) to fill the map in any way. However, the SOMTrain class provides a couple of conveniences:</p>
<p class="p2"><br></p>
<p class="p4"><span class="s7"><span class="Apple-tab-span">	</span></span><span class="s5">SOMTrain</span><span class="s7">.initBufRand(b); </span>// Each node gets an independent randomly-distributed co-ordinate (i.e. ignores where its neighbours might be)</p>
<p class="p2"><br></p>
<p class="p4"><span class="s7"><span class="Apple-tab-span">	</span></span><span class="s5">SOMTrain</span><span class="s7">.initBufGrid(b, netsize, numdims, spinmatrix); </span>// The nodes are initialised as a grid, oriented according to the supplied rotation matrix (nested array, size [numinputdims][numdims])</p>
<p class="p2"><br></p>
<p class="p4"><span class="s7"><span class="Apple-tab-span">	</span></span><span class="s5">SOMTrain</span><span class="s7">.initBufGridRand(b, netsize, numdims); </span>// The nodes are initialised as a grid, randomly oriented in the input space</p>
<p class="p2"><br></p>
<p class="p3">The first of these options is simple but not recommended: starting with a completely random arrangement of nodes is quite likely to lead to a resulting map with "twists" in it, which may be an unhelpful local-minimum solution.</p>
<p class="p2"><br></p>
<p class="p2"><br></p>
<p class="p3"><b>Examples</b></p>
<p class="p2"><br></p>
<p class="p3">An SOM will try to fit a smooth-ish surface to the given data, so as a test case let's create a single sine-wave undulation in a Buffer, and see if a one-dimensional SOM can fit nicely to that sinewave. (For higher dimension examples see <a href="SOMTrain_2D_example.html"><span class="s8">SOMTrain_2D_example</span></a>, <a href="SOMTrain_3D_example.html"><span class="s9">SOMTrain_3D_example</span></a>)</p>
<p class="p2"><br></p>
<p class="p3">s.boot;</p>
<p class="p3">~numnodes = 20;</p>
<p class="p3">~traindur = 10000;</p>
<p class="p4">// First create the data</p>
<p class="p3">~dataspace = <span class="s5">Buffer</span>.alloc(s, s.sampleRate); <span class="s6">// Let's have 1 second of data</span></p>
<p class="p3">~dataspace.sine1([1], <span class="s5">true</span>, <span class="s5">false</span>);</p>
<p class="p3">~dataspace.plot;</p>
<p class="p4">// Allocate space for the SOM, and initialise it</p>
<p class="p3">~som = <span class="s5">SOMTrain</span>.allocBuf(s, ~numnodes, 1, 2);</p>
<p class="p4">// Typically wewouldn't recommend initBufRand, so let's use initBufGridRand:</p>
<p class="p3"><span class="s5">SOMTrain</span>.initBufGridRand(~som, ~numnodes, 1, [1,0], 1.5);</p>
<p class="p4"><span class="s7">~som.plot(minval:</span><span class="s5">nil</span><span class="s7">.dup, maxval: </span><span class="s5">nil</span><span class="s7">.dup); </span>// Quick peek at how the SOM nodes are initially positioned</p>
<p class="p2"><br></p>
<p class="p4">// Also, just for demonstration purposes, we're going to write the error values to a buffer so we can see how they evolve</p>
<p class="p3">~errorvals = <span class="s5">Buffer</span>.alloc(s, ~traindur);</p>
<p class="p2"><br></p>
<p class="p4">// Now train the SOM. To train it with data "randomly sampled" from the data buffer, we just wiggle the phase around and feed [phase, val] to the SOM.</p>
<p class="p3">(</p>
<p class="p3">x = {</p>
<p class="p3"><span class="Apple-tab-span">	</span><span class="s5">var</span> phase, val, remain, err, loc, trig;</p>
<p class="p3"><span class="Apple-tab-span">	</span>trig = 1;</p>
<p class="p3"><span class="Apple-tab-span">	</span>phase = <span class="s5">WhiteNoise</span>.kr.range(0, ~dataspace.numFrames-1);</p>
<p class="p3"><span class="Apple-tab-span">	</span>val = <span class="s5">BufRd</span>.kr(1, ~dataspace, phase, 1);</p>
<p class="p3"><span class="Apple-tab-span">	</span>phase.poll(trig, label: <span class="s10">"phase"</span>);</p>
<p class="p3"><span class="Apple-tab-span">	</span>val.poll(trig, label: <span class="s10">"val"</span>);</p>
<p class="p4"><span class="s7"><span class="Apple-tab-span">	</span></span>// Note: phase is rescaled to 0--2 range here, so it can be similar range to other dimension</p>
<p class="p3"><span class="Apple-tab-span">	</span># remain, err, loc = <span class="s5">SOMTrain</span>.kr(~som, [phase*2/s.sampleRate, val], ~numnodes, 1, ~traindur, nhood: 0.75, gate: trig);</p>
<p class="p3"><span class="Apple-tab-span">	</span>remain.poll(trig, label: <span class="s10">"remain"</span>);</p>
<p class="p3"><span class="Apple-tab-span">	</span>err.poll(trig, label: <span class="s10">"err"</span>);</p>
<p class="p3"><span class="Apple-tab-span">	</span>loc.poll(trig, label: <span class="s10">"wrote at frame index"</span>);</p>
<p class="p3"><span class="Apple-tab-span">	</span><span class="s5">BufWr</span>.kr(err.sqrt, ~errorvals, ~traindur-remain);</p>
<p class="p3"><span class="Apple-tab-span">	</span><span class="s5">Out</span>.ar(0, <span class="s5">Pan2</span>.ar(<span class="s5">K2A</span>.ar(val) * 0.01));</p>
<p class="p3">}.play</p>
<p class="p3">)</p>
<p class="p4">// Once the training is finished (i.e. once "remain" drops to zero):</p>
<p class="p3">x.free;</p>
<p class="p4">// We should be able to look at the 2D data points represented in the SOM and see if they line up with the sinewave.</p>
<p class="p4">// This plot is nice if you have OctaveSC installed:</p>
<p class="p3">o = <span class="s5">OctaveSC</span>.new;</p>
<p class="p3">o.init</p>
<p class="p3">(</p>
<p class="p3">~som.loadToFloatArray(action: {<span class="s5">|arr|</span></p>
<p class="p3"><span class="Apple-tab-span">	</span>o[<span class="s11">\xtemp</span>] = arr[0,2..].as(<span class="s5">Array</span>).postln;</p>
<p class="p3"><span class="Apple-tab-span">	</span>o[<span class="s11">\ytemp</span>] = arr[1,3..].as(<span class="s5">Array</span>).postln;</p>
<p class="p5"><span class="s7"><span class="Apple-tab-span">	</span>o.(</span>"figure; hold off"<span class="s7">);</span></p>
<p class="p5"><span class="s7"><span class="Apple-tab-span">	</span>o.(</span>"plot(xtemp, ytemp, '-@')"<span class="s7">);</span></p>
<p class="p5"><span class="s7"><span class="Apple-tab-span">	</span>o.(</span>"hold on"<span class="s7">);</span></p>
<p class="p5"><span class="s7"><span class="Apple-tab-span">	</span>o.(</span>"spls = [0:0.01:2]; plot(spls, sin(spls*2*pi/2))"<span class="s7">);</span></p>
<p class="p3">});</p>
<p class="p3">);</p>
<p class="p4">// Otherwise here's a quick 2D plot in pure SC - does it look like the sinewave you started with?</p>
<p class="p3">(</p>
<p class="p3">~som.loadToFloatArray(action: {<span class="s5">|arr|</span> {</p>
<p class="p3"><span class="Apple-tab-span">	</span>w = <span class="s5">GUI</span>.window.new(<span class="s10">"SOM nodes"</span>, <span class="s5">Rect</span>(200, 800, 600, 600)); <span class="s6">// SCWindow</span></p>
<p class="p3"><span class="Apple-tab-span">	</span>w.view.background = <span class="s5">Color</span>.white;</p>
<p class="p3"><span class="Apple-tab-span">	</span>(0,2..arr.size-1).do{<span class="s5">|index|</span></p>
<p class="p3"><span class="Apple-tab-span">	</span><span class="Apple-tab-span">	</span><span class="s5">GUI</span>.staticText.new(w, <span class="s5">Rect</span>(</p>
<p class="p3"><span class="Apple-tab-span">	</span><span class="Apple-tab-span">	</span><span class="Apple-tab-span">	</span><span class="Apple-tab-span">	</span>arr[index] * 600 / 2,<span class="Apple-converted-space"> </span></p>
<p class="p3"><span class="Apple-tab-span">	</span><span class="Apple-tab-span">	</span><span class="Apple-tab-span">	</span><span class="Apple-tab-span">	</span>600 - (arr[index+1] + 1 * 300), 10, 10)).string_(<span class="s10">"x"</span>) <span class="s6">// SCStaticText</span></p>
<p class="p3"><span class="Apple-tab-span">	</span>};</p>
<p class="p3"><span class="Apple-tab-span">	</span>w.front;</p>
<p class="p3">}.defer})</p>
<p class="p3">)</p>
<p class="p2"><br></p>
<p class="p4">// How do the errors look? Should be a very noisy curve gradually decreasing, but won't necessarily stabilise</p>
<p class="p3">~errorvals.plot(maxval: <span class="s5">nil</span>, minval: <span class="s5">nil</span>);</p>
</body>
</html>
